package com.autonavi.yunda.yunji.core.engine.context;

import com.autonavi.yunda.yunji.common.json.JSONDateSerial;
import com.autonavi.yunda.yunji.core.enums.AccessStatus;
import com.autonavi.yunda.yunji.core.enums.NodeStatus;
import com.autonavi.yunda.yunji.core.enums.NodeType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * @author cizhong.wcs
 * @date 2022/5/30 15:01
 */
@Data
public class NodeContext {
    @JsonIgnore
    private Node node;
    private volatile NodeStatus status;
    private Map<String, AccessStatus> nextNodesAccessStatus = new ConcurrentHashMap<>();
    @JsonIgnore
    private final EngineContext engineContext;
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Object nodeOutput;
    private String errorMessage;
    /**
     * 节点异常
     */
    @JsonIgnore
    private Throwable exception;
    @JsonSerialize(using = JSONDateSerial.class)
    private long startTime;
    @JsonSerialize(using = JSONDateSerial.class)
    private long endTime;
    private long executeTime;
    public Future<?> executeFuture;

    public NodeContext(Node node, EngineContext engineContext) {
        this.node = node;
        this.status = NodeStatus.INIT;
        this.engineContext = engineContext;
    }

    public String getName() {
        return node.getName();
    }

    public void setStatus(NodeStatus status) {
        synchronized (engineContext) {
            if (status.isFail()) {
                engineContext.finishedStatus = -1;
            } else if (status.isFinishedStatus() && getNode().getNodeType().equals(NodeType.end)) {
                engineContext.finishedStatus = 1;
            }
            this.status = status;
        }
    }

    @JsonIgnore
    public List<NodeContext> getPreviousNodeContext() {
        return node.getPreviousNodes().stream().map(engineContext.nameNodeMap::get).collect(Collectors.toList());
    }

    @JsonIgnore
    public List<NodeContext> getNextNodeContext() {
        return node.getNextNodes().stream().map(engineContext.nameNodeMap::get).collect(Collectors.toList());
    }

    @JsonIgnore
    public List<NodeContext> getDefaultNextNodeContext() {
        return node.getDefaultNextNodes().stream().map(engineContext.nameNodeMap::get).collect(Collectors.toList());
    }

    @JsonIgnore
    public String getNodeSwitcherFunctionName(String nextNodeName) {
        return node.getNodeSwitcherFunctionMap().get(nextNodeName);
    }

    @JsonIgnore
    public String getNodeSwitcherParamNameType(String switcherFunName, String paramName) {
        return node.getNodeSwitcherParamNameTypeMap().get(switcherFunName).get(paramName);
    }

    /**
     * 执行前检查,判断改节点是否可执行,如果可执行则设置为RUNNING状态
     * 满足以下条件其中之一就可以执行
     * 1. 无前置节点
     * 2. 当前节点不为Running和终结状态,且前置节点都为终结状态.
     */
    public boolean executeCheckAndSetRunning() {
        synchronized (engineContext) {
            if (getPreviousNodeContext().isEmpty()) {
                return true;
            }
            if (status.isRunning() || status.isFinishedStatus() || failCheck()) {
                return false;
            }
            boolean canExecute = getPreviousNodeContext().stream().allMatch(nodeContext -> nodeContext.status.isFinishedStatus());
            if (!canExecute) {
                return false;
            }
            setStartTime(System.currentTimeMillis());
            setStatus(NodeStatus.RUNNING);
            return true;
        }
    }

    /**
     * 检查链路中是否有节点失败
     */
    public boolean failCheck() {
        return engineContext.finishedStatus == -1;
    }

    /**
     * 检查当前节点是否不可达
     * @param accessStatus 上游指定当前节点状态
     * @return 不可达返回true
     */
    public boolean inaccessibleCheck(AccessStatus accessStatus) {
        synchronized (engineContext) {
            // 检查所有父节点可达性,父节点为空则直接可达,如start
            if (getPreviousNodeContext().isEmpty()) {
                return false;
            }
            // 所有父节点都不可达,则当前节点不可达
            if (getPreviousNodeContext().stream().allMatch(nodeContext -> nodeContext.status.isInaccessible())) {
                return true;
            }
            // 父节点经过判断
            if (getPreviousNodeContext().stream()
                    .map(c -> c.getNextNodesAccessStatus().get(getName()))
                    .allMatch(AccessStatus.INACCESSIBLE::equals)) {
                return true;
            }
            return accessStatus.equals(AccessStatus.INACCESSIBLE) && getPreviousNodeContext().size() == 1;
        }
    }

    public void addNextNodeAccessStatus(String name, AccessStatus accessStatus) {
        this.nextNodesAccessStatus.put(name, accessStatus);
    }
}
